// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: support message brief record
// feature: QoS Profile Inheritance and Composition
// feature: simplify xml profile
// ------------------------------------------------------------------

#include <xmlparser/XMLProfileManagerPart.h>
#include <xmlparser/XMLTreePart.h>

#include <tinyxml2.h>

#ifdef _WIN32
#include <windows.h>
#endif  // ifdef _WIN32

#include <cstdlib>
#include <elog/Log.hpp>

namespace vbsutil {
namespace xmlparser {

using part_map_iterator_t = participant_map_t::iterator;
using publ_map_iterator_t = publisher_map_t::iterator;
using subs_map_iterator_t = subscriber_map_t::iterator;
using topic_map_iterator_t = topic_map_t::iterator;

using xmlfile_map_iterator_t = xmlfiles_map_t::iterator;
set_flags_map_t XMLProfileManagerPart::participant_profile_set_flags_;
set_flags_map_t XMLProfileManagerPart::publisher_profile_set_flags_;
set_flags_map_t XMLProfileManagerPart::subscriber_profile_set_flags_;
std::shared_ptr<ParticipantAttributes> default_participant_attributes;
PublisherAttributes default_publisher_attributes;
SubscriberAttributes default_subscriber_attributes;
TopicAttributes default_topic_attributes;
bool XMLProfileManagerPart::isLoadBuiltinProfileStr = false;
std::string XMLProfileManagerPart::currentXmlPath = "";

std::map<std::string, XMLP_ret> XMLProfileManagerPart::xml_files_part_;
std::recursive_mutex XMLProfileManagerPart::mtx_xml_files_part_;
std::map<std::string, up_participant_t> XMLProfileManagerPart::participant_profiles_;
std::mutex XMLProfileManagerPart::mtx_xml_participant_profiles_;
std::map<std::string, up_publisher_t> XMLProfileManagerPart::publisher_profiles_;
std::mutex XMLProfileManagerPart::mtx_xml_publisher_profiles_;
std::map<std::string, up_subscriber_t> XMLProfileManagerPart::subscriber_profiles_;
std::mutex XMLProfileManagerPart::mtx_xml_subscriber_profiles_;
std::map<std::string, up_topic_t> XMLProfileManagerPart::topic_profiles_;
std::mutex XMLProfileManagerPart::mtx_xml_topic_profiles_;
sp_transport_map_t XMLProfileManagerPart::transport_profiles_;
std::mutex XMLProfileManagerPart::mtx_xml_transport_profiles_;

up_base_node_t XMLProfileManagerPart::xmlFile_root_node;
up_base_node_t XMLProfileManagerPart::xmlString_root_node;
up_base_node_t XMLProfileManagerPart::xmlNode_root_node;
std::vector<std::string> XMLProfileManagerPart::userTransportsId;

XMLP_ret XMLProfileManagerPart::getParticipantAttributes(const std::string& profile_name, ParticipantAttributes& atts,
                                                         bool log_error) {
    std::lock_guard<std::mutex> lk(mtx_xml_participant_profiles_);
    part_map_iterator_t it = participant_profiles_.find(profile_name);
    if (it == participant_profiles_.end()) {
        if (log_error) {
            elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Profile '" << profile_name << "' not found");
        }
        return XMLP_ret::XML_ERROR;
    }
    atts = *(it->second);
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::getPublisherAttributes(const std::string& profile_name, PublisherAttributes& atts,
                                                       bool log_error) {
    std::lock_guard<std::mutex> lk(mtx_xml_publisher_profiles_);
    publ_map_iterator_t it = publisher_profiles_.find(profile_name);
    if (it == publisher_profiles_.end()) {
        if (log_error) {
            elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Profile '" << profile_name << "' not found");
        }
        return XMLP_ret::XML_ERROR;
    }
    atts = *(it->second);
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::getSubscriberAttributes(const std::string& profile_name, SubscriberAttributes& atts,
                                                        bool log_error) {
    std::lock_guard<std::mutex> lk(mtx_xml_subscriber_profiles_);
    subs_map_iterator_t it = subscriber_profiles_.find(profile_name);
    if (it == subscriber_profiles_.end()) {
        if (log_error) {
            elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Profile '" << profile_name << "' not found");
        }
        return XMLP_ret::XML_ERROR;
    }
    atts = *(it->second);
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::getTopicAttributes(const std::string& profile_name, TopicAttributes& atts) {
    std::lock_guard<std::mutex> lk(mtx_xml_topic_profiles_);
    topic_map_iterator_t it = topic_profiles_.find(profile_name);
    if (it == topic_profiles_.end()) {
        elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Profile '" << profile_name << "' not found");
        return XMLP_ret::XML_ERROR;
    }
    atts = *(it->second);
    return XMLP_ret::XML_OK;
}

bool XMLProfileManagerPart::insertTransportById(const std::string& transport_id, sp_transport_t transport) {
    std::lock_guard<std::mutex> lk(mtx_xml_transport_profiles_);
    if (transport_profiles_.find(transport_id) == transport_profiles_.end()) {
        transport_profiles_[transport_id] = std::move(transport);
        return true;
    }
    elogWarning(XMLPARSER,
                "Error adding the transport " << transport_id << ". There is other transport with the same id");
    return false;
}

sp_transport_t XMLProfileManagerPart::getTransportById(const std::string& transport_id) {
    std::lock_guard<std::mutex> lk(mtx_xml_transport_profiles_);
    if (transport_profiles_.find(transport_id) != transport_profiles_.end()) {
        return transport_profiles_[transport_id];
    }
    return nullptr;
}

XMLP_ret XMLProfileManagerPart::extractParticipantProfile(up_base_node_t& profile, const std::string& profile_lib_name,
                                                          std::vector<bool>& set_flags) {
    std::string profile_name = "";
    std::lock_guard<std::mutex> lk(mtx_xml_participant_profiles_);
    node_participant_t* node_part = dynamic_cast<node_participant_t*>(profile.get());
    if (!profile_lib_name.empty()) {
        if (participant_profiles_.find(profile_lib_name) != participant_profiles_.end()) {
            XMLParserpart::participantProfileCompareToUpdate(participant_profiles_[profile_lib_name].get(),
                                                             participant_profile_set_flags_[profile_lib_name],
                                                             node_part->get(), set_flags);
        } else {
            participant_profiles_.emplace(profile_lib_name,
                                          up_participant_t(new ParticipantAttributes(*node_part->get())));
            participant_profile_set_flags_.emplace(profile_lib_name, set_flags);
        }
    }

    node_att_map_cit_t it = node_part->getAttributes().find(xmlString::PROFILE_NAME);
    if (it == node_part->getAttributes().end() || it->second.empty()) {
        return XMLP_ret::XML_OK;
    }

    if (profile_lib_name.empty()) {
        profile_name = it->second;
    } else {
        profile_name = profile_lib_name + "::" + it->second;
    }

    participant_profile_set_flags_.emplace(profile_name, set_flags);
    std::pair<part_map_iterator_t, bool> emplace = participant_profiles_.emplace(profile_name, node_part->getData());
    if (false == emplace.second) {
        elogError(XMLPARSER, RetCode_t::RETCODE_ERROR, "Error adding profile '" << profile_name << "'");
        return XMLP_ret::XML_ERROR;
    }

    it = node_part->getAttributes().find(xmlString::DEFAULT_PROF);
    if (it != node_part->getAttributes().end() && it->second == "true") {  // Set as default profile
        // +V+ TODO: LOG ERROR IN SECOND ATTEMPT
        if (emplace.first != participant_profiles_.end() && emplace.first->second) {
            getDefaultParticipantAttributes() = *(emplace.first->second.get());
        }
    }
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::extractPublisherProfile(up_base_node_t& profile, const std::string& profile_lib_name,
                                                        std::vector<bool>& set_flags) {
    std::string profile_name = "";
    std::lock_guard<std::mutex> lk(mtx_xml_publisher_profiles_);
    node_publisher_t* node_part = dynamic_cast<node_publisher_t*>(profile.get());
    if (!profile_lib_name.empty()) {
        if (publisher_profiles_.find(profile_lib_name) != publisher_profiles_.end()) {
            XMLParserpart::publisherProfileCompareToUpdate(publisher_profiles_[profile_lib_name].get(),
                                                           publisher_profile_set_flags_[profile_lib_name],
                                                           node_part->get(), set_flags);
        } else {
            publisher_profiles_.emplace(profile_lib_name, up_publisher_t(new PublisherAttributes(*node_part->get())));
            publisher_profile_set_flags_.emplace(profile_lib_name, set_flags);
        }
    }

    node_att_map_cit_t it = node_part->getAttributes().find(xmlString::PROFILE_NAME);
    if (it == node_part->getAttributes().end() || it->second.empty()) {
        return XMLP_ret::XML_OK;
    }

    if (profile_lib_name.empty()) {
        profile_name = it->second;
    } else {
        profile_name = profile_lib_name + "::" + it->second;
    }

    publisher_profile_set_flags_.emplace(profile_name, set_flags);
    std::pair<publ_map_iterator_t, bool> emplace = publisher_profiles_.emplace(profile_name, node_part->getData());
    if (false == emplace.second) {
        elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Error adding profile '" << profile_name << "'");
        return XMLP_ret::XML_ERROR;
    }

    it = node_part->getAttributes().find(xmlString::DEFAULT_PROF);
    if (it != node_part->getAttributes().end() && it->second == "true") {  // Set as default profile
        // +V+ TODO: LOG ERROR IN SECOND ATTEMPT
        if (emplace.first != publisher_profiles_.end() && emplace.first->second) {
            default_publisher_attributes = *(emplace.first->second.get());
        }
    }
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::extractSubscriberProfile(up_base_node_t& profile, const std::string& profile_lib_name,
                                                         std::vector<bool>& set_flags) {
    std::string profile_name = "";
    std::lock_guard<std::mutex> lk(mtx_xml_subscriber_profiles_);
    node_subscriber_t* node_part = dynamic_cast<node_subscriber_t*>(profile.get());
    if (!profile_lib_name.empty()) {
        if (subscriber_profiles_.find(profile_lib_name) != subscriber_profiles_.end()) {
            XMLParserpart::subscriberProfileCompareToUpdate(subscriber_profiles_[profile_lib_name].get(),
                                                            subscriber_profile_set_flags_[profile_lib_name],
                                                            node_part->get(), set_flags);
        } else {
            subscriber_profiles_.emplace(profile_lib_name,
                                         up_subscriber_t(new SubscriberAttributes(*node_part->get())));
            subscriber_profile_set_flags_.emplace(profile_lib_name, set_flags);
        }
    }

    node_att_map_cit_t it = node_part->getAttributes().find(xmlString::PROFILE_NAME);
    if (it == node_part->getAttributes().end() || it->second.empty()) {
        return XMLP_ret::XML_OK;
    }

    if (profile_lib_name.empty()) {
        profile_name = it->second;
    } else {
        profile_name = profile_lib_name + "::" + it->second;
    }

    subscriber_profile_set_flags_.emplace(profile_name, set_flags);
    std::pair<subs_map_iterator_t, bool> emplace = subscriber_profiles_.emplace(profile_name, node_part->getData());
    if (false == emplace.second) {
        elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Error adding profile '" << profile_name << "'");
        return XMLP_ret::XML_ERROR;
    }

    it = node_part->getAttributes().find(xmlString::DEFAULT_PROF);
    if (it != node_part->getAttributes().end() && it->second == "true") {  // Set as default profile
        // +V+ TODO: LOG ERROR IN SECOND ATTEMPT
        if (emplace.first != subscriber_profiles_.end() && emplace.first->second) {
            default_subscriber_attributes = *(emplace.first->second.get());
        }
    }
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::extractTopicProfile(up_base_node_t& profile) {
    std::string profile_name = "";
    std::lock_guard<std::mutex> lk(mtx_xml_topic_profiles_);
    node_topic_t* node_topic = dynamic_cast<node_topic_t*>(profile.get());
    node_att_map_cit_t it = node_topic->getAttributes().find(xmlString::PROFILE_NAME);
    if (it == node_topic->getAttributes().end() || it->second.empty()) {
        elogError(XMLPARSER, RetCode_t::RETCODE_NO_DATA, "Error adding profile");
        return XMLP_ret::XML_ERROR;
    }

    profile_name = it->second;

    auto& stored_profiles = XMLProfileManagerPart::topic_profiles_;
    if (stored_profiles.find(profile_name) != stored_profiles.end()) {
        elogError(XMLPARSER, RetCode_t::RETCODE_ILLEGAL_OPERATION,
                  "data_topic profile '" << profile_name << "' already exist.");
        return XMLP_ret::XML_ERROR;
    }

    std::pair<topic_map_iterator_t, bool> emplace = topic_profiles_.emplace(profile_name, node_topic->getData());
    if (false == emplace.second) {
        elogError(XMLPARSER, RetCode_t::RETCODE_ILLEGAL_OPERATION, "Error adding profile '" << profile_name << "'");
        return XMLP_ret::XML_ERROR;
    }

    it = node_topic->getAttributes().find(xmlString::DEFAULT_PROF);
    if (it != node_topic->getAttributes().end() && it->second == "true") {
        // +V+ TODO: LOG ERROR IN SECOND ATTEMPT
        if (emplace.first != topic_profiles_.end() && emplace.first->second) {
            default_topic_attributes = *(emplace.first->second.get());
        }
    }
    return XMLP_ret::XML_OK;
}

void XMLProfileManagerPart::setDefaultProfile(const std::string& name) {
    {
        std::lock_guard<std::mutex> lk(mtx_xml_participant_profiles_);
        if (participant_profiles_.find(name) != participant_profiles_.end()) {
            getDefaultParticipantAttributes() = *participant_profiles_[name].get();
            logDebug(XMLPARSER, "participant profile '" << name << "' set to default profile");
        }
    }

    {
        std::lock_guard<std::mutex> lk(mtx_xml_publisher_profiles_);
        if (publisher_profiles_.find(name) != publisher_profiles_.end()) {
            default_publisher_attributes = *publisher_profiles_[name].get();
            logDebug(XMLPARSER, "data_writer profile '" << name << "' set to default profile");
        }
    }

    {
        std::lock_guard<std::mutex> lk(mtx_xml_subscriber_profiles_);
        if (subscriber_profiles_.find(name) != subscriber_profiles_.end()) {
            default_subscriber_attributes = *subscriber_profiles_[name].get();
            logDebug(XMLPARSER, "data_reader profile '" << name << "' set to default profile");
        }
    }
}

ParticipantAttributes& XMLProfileManagerPart::getDefaultParticipantAttributes() {
    if (default_participant_attributes == nullptr) {
        // default_participant_attributes = new ParticipantAttributes;
        default_participant_attributes = std::make_shared<ParticipantAttributes>();
    }
    return *default_participant_attributes;
}

PublisherAttributes& XMLProfileManagerPart::getDefaultPublisherAttributes() {
    return default_publisher_attributes;
}

SubscriberAttributes& XMLProfileManagerPart::getDefaultSubscriberAttributes() {
    return default_subscriber_attributes;
}

TopicAttributes& XMLProfileManagerPart::getDefaultTopicAttributes() {
    return default_topic_attributes;
}

bool XMLProfileManagerPart::getInheritanceCombinationBaseInfo(std::string name) {
    if (name != xmlString::BASE_NAME) {
        return false;
    }
    if (XMLProfileManagerPart::isLoadBuiltinProfileStr == true) {
        return true;
    }
    XMLProfileManagerPart::isLoadBuiltinProfileStr = true;
    // basexml  ——>BuiltinProfiles.documentationONLY.xml
    XMLParserpart::fillBuildinProfilesAttributes();
    return true;
}

void extractPath(const std::string& filename) {
    size_t lastSlashIndex = filename.find_last_of("/\\");
    if (lastSlashIndex != std::string::npos) {
        XMLProfileManagerPart::currentXmlPath = filename.substr(0, lastSlashIndex + 1);
    }
}

XMLP_ret XMLProfileManagerPart::loadXMLFile(const std::string& filename) {
    if (filename.empty()) {
        elogError(XMLPARSER, RetCode_t::RETCODE_BAD_PARAMETER, "Error loading XML file, filename empty");
        return XMLP_ret::XML_ERROR;
    }
    std::lock_guard<std::recursive_mutex> lk(mtx_xml_files_part_);

    xmlfile_map_iterator_t it = xml_files_part_.find(filename);
    if ((it != xml_files_part_.end()) && (XMLP_ret::XML_OK == it->second)) {
        logDebug(XMLPARSER, "XML file '" << filename << "' already parsed");
        return XMLP_ret::XML_OK;
    }

    extractPath(filename);
    XMLP_ret loaded_ret = XMLParserpart::loadXML(filename, XMLProfileManagerPart::xmlFile_root_node);
    (void)xml_files_part_.emplace(filename, loaded_ret);

    if (loaded_ret != XMLP_ret::XML_OK) {
        if (filename != std::string(xmlString::DEFAULT_ERTPS_PROFILES)) {
            elogError(XMLPARSER, RetCode_t::RETCODE_BAD_PARAMETER, "Error parsing '" << filename << "'");
        }
        return XMLP_ret::XML_ERROR;
    }

    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::loadXMLString(const char* data, size_t length) {
    XMLP_ret loaded_ret = XMLParserpart::loadXML(data, length, XMLProfileManagerPart::xmlString_root_node);
    if (loaded_ret != XMLP_ret::XML_OK) {
        elogError(XMLPARSER, RetCode_t::RETCODE_ERROR, "XMLParserpart, Error parsing string");
        return XMLP_ret::XML_ERROR;
    }
    return XMLP_ret::XML_OK;
}

XMLP_ret XMLProfileManagerPart::loadXMLNode(tinyxml2::XMLDocument& doc) {
    XMLP_ret loaded_ret = XMLParserpart::loadXML(doc, XMLProfileManagerPart::xmlNode_root_node);

    if (loaded_ret != XMLP_ret::XML_OK) {
        elogError(XMLPARSER, RetCode_t::RETCODE_ERROR, "XMLParserpart, Error parsing node");
        return XMLP_ret::XML_ERROR;
    }

    logDebug(XMLPARSER, "Node parsed successfully");
    return XMLP_ret::XML_OK;
}

}  // namespace xmlparser
}  // namespace vbsutil
